#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/err.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/timer.h>
#include <linux/atomic.h>




#define TIMER_OPEN      _IO('L', 0)
#define TIMER_CLOSE     _IO('L', 1)
#define TIMER_SET       _IOW('L', 2, int)

#define CLASS_NAME  "dev_test"
#define DEIVCE_NAME "dev_test"

struct char_device {
    dev_t           dev_num;    // 设备号
    int             major;      
    int             minor;  
    struct cdev     char_dev;   // 字符设备
    struct class    *class;     // 类
    struct device   *device;    // 设备

    char            kbuf[32];   // 缓冲区buf
    int             count;      // 定时器定时值
};

struct char_device  dev1;



static void timer_cb(struct timer_list *t);
DEFINE_TIMER(timer_test, timer_cb);
static void timer_cb(struct timer_list *t)
{
    printk(KERN_EMERG "timer_cb\n");
    mod_timer(&timer_test, jiffies_64 + msecs_to_jiffies(dev1.count));
}

// 打开设备操作
static int cdev_open(struct inode *inode, struct file *file)
{
    // 设备配置私有化。
    file->private_data = container_of(inode->i_cdev, struct char_device, char_dev);
    printk(KERN_EMERG "cdev_open!\n");
    return 0;
}

// 关闭设备操作
static int cdev_release(struct inode *inode, struct file *file)
{
    printk(KERN_EMERG "cdev_realease!\n");
    return 0;
}


ssize_t cdev_read (struct file *file, char __user *ubuf, size_t size, loff_t *off)
{
    struct char_device *test_dev = (struct char_device *)file->private_data;
    
    // 内核空间 ---数据---> 用户空间 
    if (copy_to_user(ubuf, test_dev->kbuf, size) != 0)
    {
        printk(KERN_EMERG "copy to user error!\n");
        return -1;
    }
    printk(KERN_EMERG "cdev_read \n");
    return size;
}

ssize_t cdev_write (struct file *file, const char __user *ubuf, size_t size, loff_t *off)
{
    struct char_device *test_dev = (struct char_device *)file->private_data;
    
    // 内核空间 <---数据--- 用户空间 
    if (copy_from_user(test_dev->kbuf, ubuf, size) != 0)
    {
        printk(KERN_EMERG "copy_from_user error!\n");
        return -1;
    }
    printk(KERN_EMERG "cdev_write size: %ld \n", size);
    printk(KERN_EMERG "%s\n", test_dev->kbuf);

    return size;
}


long cdev_unlocked_ioctl (struct file *file, unsigned int cmd, unsigned long arg)
{
    struct char_device *test_dev = (struct char_device *)file->private_data;
    switch(cmd) {
        case TIMER_OPEN:
            add_timer(&timer_test);
            break;
        case TIMER_CLOSE:
            del_timer(&timer_test);
            break;
        case TIMER_SET:
            test_dev->count = arg;
            timer_test.expires = jiffies_64 + msecs_to_jiffies(test_dev->count);
            break;
        default:
            break;
    }

    return 0;
}

struct file_operations cdev_fops = {
    .owner = THIS_MODULE,
    .open = cdev_open,
    .release = cdev_release,
    .read = cdev_read,
    .write = cdev_write,
    .unlocked_ioctl = cdev_unlocked_ioctl,
};

static int __init llseek_test_init(void)
{
    int ret;

    // 申请设备号
    ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_chrdev");
    if (ret < 0)
    {
        printk(KERN_EMERG "alloc chrdev is error!\n");
        goto err_chrdev_region;
    }
    printk(KERN_EMERG "alloc chrdev\n");
    dev1.major = MAJOR(dev1.dev_num);
    dev1.minor = MINOR(dev1.dev_num);
    printk(KERN_EMERG "major : %d\t minor : %d\n",dev1.major, dev1.minor);

    // 创建字符设备
    dev1.char_dev.owner = THIS_MODULE;
    cdev_init(&dev1.char_dev, &cdev_fops);
    ret = cdev_add(&dev1.char_dev, dev1.dev_num, 1);
    if (ret < 0)
    {
        printk(KERN_EMERG "cdev_add error\r");
        goto err_cdev_add;
    }

    // 创建类和设备节点
    dev1.class  = class_create(THIS_MODULE, CLASS_NAME);
    if (IS_ERR(dev1.class))
    {
        ret = PTR_ERR(dev1.class);
        goto err_class_create;
    }

    dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, DEIVCE_NAME);
    if (IS_ERR(dev1.device))
    {
        ret = PTR_ERR(dev1.device);
        goto err_device_create;
    }
    return 0;

err_device_create:
    class_destroy(dev1.class);                  // 删除类
err_class_create:
    cdev_del(&dev1.char_dev);                   // 删除字符设备
err_cdev_add:
    unregister_chrdev_region(dev1.dev_num, 1);  // 删除设备号
err_chrdev_region:
    return ret;                                 //返回错误码
}

static void __exit llseek_test_exit(void)
{
    device_destroy(dev1.class, dev1.dev_num);   // 删除设备节点
    class_destroy(dev1.class);                  // 删除类
    cdev_del(&dev1.char_dev);                   // 删除字符设备
    unregister_chrdev_region(dev1.dev_num, 1);  // 删除设备号
    printk(KERN_EMERG "moule_exit\n");
}

module_init(llseek_test_init);
module_exit(llseek_test_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("treasure1029");
